/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets;

import com.codemagi.util.Utils;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.log4j.Logger;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;

/**
 * Reduces size of JavaScript files by removing whitespace (and other operations). 
 *
 * The actual transformations performed are based on the YUI Compressor from Yahoo!
 * @see http://developer.yahoo.com/yui/compressor
 *
 * @version 1.0
 * @author August Detlefsen for CodeMagi, Inc.
 */
public class MinifyJavascriptFilter implements Filter {

    Logger log = Logger.getLogger(this.getClass());

    private ServletContext application = null;

    //cache to remember which files have already been minified
    private static ReferenceMap cache = new ReferenceMap();


    /**
     * @todo   Add init params for munge, verbose, preserveAllSemiColons, disableOptimizations
     */
    public void init(FilterConfig config) 
	throws ServletException {

	log.debug("MinifyJavascriptFilter.init()");

	application = config.getServletContext();
	if (application == null) throw new ServletException("Unable to initialize MinifyJavascriptFilter: ServletContext unavailable.");
    }


    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
	throws ServletException, IOException {

	try {
	    //find the file to output
	    String location = ((HttpServletRequest)request).getServletPath();
	    log.debug("doFilter: " + location);

	    //check if this file has already been minified
	    String minified = (String)cache.get(location);
	    if ( Utils.isEmpty(minified) ) {
		log.debug("Minifying: " + location);

		//read the CSS file
		URL url                  = application.getResource(location);
		URLConnection connection = url.openConnection();
		BufferedReader in        = new BufferedReader( new InputStreamReader( connection.getInputStream() ) );

		//get the output stream to write to 
		StringWriter writer = new StringWriter();

		//instantiate the compressor
		JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {

			public void warning(String message, String sourceName,
					    int line, String lineSource, int lineOffset) {
			    if (line < 0) {
				log.warn("\n[WARNING] " + message);
			    } else {
				log.warn("\n[WARNING] " + line + ':' + lineOffset + ':' + message);
			    }
			}
			
			public void error(String message, String sourceName,
					  int line, String lineSource, int lineOffset) {
			    if (line < 0) {
				log.error("\n[ERROR] " + message);
			    } else {
				log.error("\n[ERROR] " + line + ':' + lineOffset + ':' + message);
			    }
			}
			
			public EvaluatorException runtimeError(String message, String sourceName,
							       int line, String lineSource, int lineOffset) {
			    error(message, sourceName, line, lineSource, lineOffset);
			    return new EvaluatorException(message);
			}
		    });

		//compress the output
		compressor.compress(writer, -1, false, true, true, true);
		minified = writer.toString();

		//record in the cache that this file has been minified
		cache.put(location, minified);

		log.debug("MINIFIED (" + minified.length() + " bytes): " + minified);
            }

	    //write the minified file to the output
	    PrintWriter out = response.getWriter();
	    out.write(minified);
	    out.flush();	    

	} catch (ClassCastException cce) {
	    //no-op, just a safety measure
	}

	//forward on to other filters
	//	chain.doFilter(request, response);
    }

    /**
     * Free any resources used by this Filter.
     */
    public void destroy() {
	application = null;
    }

}
